import service from "@/service"
import utils from "@/utils"
import { ElMessage } from "element-plus"
import type { InjectionKey } from "vue"
import { createStore, Store, useStore as baseUseStore } from 'vuex'
import type { configStoreState } from "./config.store"

let configStore: Store<configStoreState>

// export type TPrefabGroup = {path: Config.ItemId[], indexPath: Config.GroupId[]}
export type TPrefabGroup = Record<Config.GroupId, Config.ItemId>

export type NFTCreatorStoreState = {
  /** 是否运算中 */
  calcloading: boolean;
  /** 是否组选中，若是，则不可暂停计算 */
  groupSelectLoading: boolean;
  /** 是否暂停计算 */
  calcPause: boolean;
  /** 计算预制数据的进度提示 */
  calcPrefabTip: string[];
  prefabGroup: TPrefabGroup[][]
  /** prefabGroup总长度 */
  prefabGroupTatal: number;
  /** 预制数据=>所有可绘制结果 */
  prefabImagePool: Config.ItemId[][];
}

export const NFTCreatorStoreKey: InjectionKey<Store<NFTCreatorStoreState>> = Symbol()

export const NFTCreatorStore = createStore<NFTCreatorStoreState>({
  state: {
    calcloading: false,
    groupSelectLoading: false,
    calcPause: false,
    calcPrefabTip: ['请仔细阅读指引，并按步骤进行操作。 ^. ^'],
    prefabGroup: [],
    prefabGroupTatal: 0,
    prefabImagePool: []
  },
  actions: {
    // 检查是否有不可用列
    async $checkIsHasDisabledColumns() {
      for (const group of configStore.state.currentGroup) {
        if (group.isRequired) {
          const items = configStore.state.currentItems.filter(k => k.gid == group.id)
          const canUsed = items.filter(k => k.max == -1 || k.currentCount < k.max)

          if (canUsed.length == 0) {
            console.log(`检查是否有不可用列: ${group.name}有不可用列`);

            return true
          }
        }
      }
      return false
    },
    /** 利用组选展开数据生成一条path */
    async $getPath({state}, prefab: TPrefabGroup) {
      /** 定义路径 */
      const path = new Array(configStore.state.currentGroup.length).fill(undefined)
      /** 可重复元素路径下标 */
      const indexPath = Object.keys(prefab).map(k => Number(k))
      // 可重复元素赋值
      indexPath.map(gid => {
        const itemId = prefab[gid]
        const item = configStore.state.currentItems.find(k => k.id == itemId) as Config.Item
        path[gid] = item && (item.max == -1 || item.currentCount < item.max) ? itemId : null
      })

      if (path.includes(null)) return path
      else {
        /** 当前路径下标，辅助forOf运算使用 */
        let pathIdx = 0

        for (const _pathItem of path) {
          // 获取本列的GroupId
          const currentGroup = configStore.state.currentGroup[pathIdx]
          // 通过组下标获取本列可用元素集合
          const getUseItemByGroupIdx = (groupIdx: number, groupId: Config.GroupId = currentGroup.id) => {
            // 获取本列下所哟历史数据
            let historyDatas = state.prefabImagePool.filter(k => indexPath.every(j => k[j] == path[j])).map(k => k[groupIdx])
            // 去重
            historyDatas = utils.dupRemove(historyDatas)
            // 提取本列数据
            let lib = configStore.state.currentItems.filter(k => k.gid == groupId)
            // HP无限 或 HP未满
            lib = lib.filter(k => k.max == -1 || k.currentCount < k.max)
            // 不能与历史数据任一重合
            lib = lib.filter(k => !historyDatas.includes(k.id))

            return lib
          }

          if (!indexPath.includes(pathIdx)) {               // 若当前下标不在组选下标内，则需要运算获取元素id
            /** 关联组生成的item实例 */
            let relationItem: Config.Item|undefined = undefined

            // 若元素与其他组关联
            if (currentGroup.rid) {
              // 获取关联组gid
              const relationGroupIndex = configStore.state.currentGroup.findIndex(k => k.id == currentGroup.rid)

              if (relationGroupIndex) {
                // 若有关联组id，则获取已生成的item
                relationItem = configStore.state.currentItems.find(k => k.id == path[relationGroupIndex])
              }
            }

             // 若元素与其他组互斥
             if (currentGroup.advId) {                      // 若存在互斥组，则将互斥组数据取并集后随机抽取赋值
              // 获取互斥组的下标
              const advIdx = configStore.state.currentGroup.findIndex(k => k.id == currentGroup.advId)

              if (pathIdx < advIdx) {                       // 互斥组仅需运算一次，这里用下标进行计算是由于组是按层级进行排序过的，所以下标也代表了层级关系
                const currentColumnItems = getUseItemByGroupIdx(pathIdx)
                const advColumnItems = getUseItemByGroupIdx(advIdx, currentGroup.advId)

                const lib = [currentColumnItems, advColumnItems]      // 生成随机组
                const libIdx = utils.randomByNum(100) % 2             // 在当前组与互斥组中随机取数
                const item = utils.randomByList(lib[libIdx])          // 在取得的组里取元素
                const _pathIdx = libIdx === 0 ? pathIdx : advIdx      // 随机组idx
                path[_pathIdx] = item?.id                             // 元素赋值
              }
            } else if (currentGroup.rid) {                  // 若存在关联组生成实例，则取对应name的元素进行赋值
              path[pathIdx] = configStore.state.currentItems.find(k => (
                // 获取当前组
                k.gid == currentGroup.id &&
                // 当前元素名须与关联元素名保持一致
                k.name == relationItem?.name
              ))?.id || null
            } else if (!indexPath.includes(pathIdx)) {      // 若不是组选路径中的数据，则在可用子元素中随机抽取赋值
              const isGetItem = utils.randomByNum(1, 0)
              // 进行非必须判定，若必选或判定结果为1，则进行取值，反之，则无需取值
              if (currentGroup.isRequired === 1 || isGetItem === 1) {
                // 获取可用元素组
                const lib = getUseItemByGroupIdx(pathIdx)
                // 随机取数
                const item = utils.randomByList(lib)
                // 赋值
                path[pathIdx] = item?.id || null
              }
            }
          }

          // 若本组非必选，则删除null
          if (path[pathIdx] == null && currentGroup.isRequired == 0) {
            path[pathIdx] = undefined
          }

          pathIdx ++
        }

        return path
      }
    },
    /** 组选 */
    async $groupSelection({state, commit}) {
      /** 配置数据 */
      const libary: Record<Config.GroupId, Config.ItemId[]> = {}
      commit('log', '正在转换配置数据为可用数据...')
      // 将配置数据转为可用数据
      configStore.state.currentGroup.forEach(k => {
        // 若是关联组或互斥组，不纳入组选范围！否则无法进行后续运算
        if (!k.rid && !k.advId) {
          libary[k.id] = configStore.state.currentItems.filter(j => j.gid == k.id).map(l => l.id)
        }
      })
      const maxSame = configStore.state.base.defaultPendantReplay;
      // y轴下标组合
      const groupY: Config.GroupId[][] = utils.choose(Object.keys(libary).map(k => Number(k)), maxSame)
      commit('log', `组选，共计${groupY.length}组数据`)
      // 遍历y轴组合下的x轴所有可能
      for (let _idx = 0; _idx < groupY.length; _idx++) {
        const groupPath = groupY[_idx];
        const curLib: Config.ItemId[][] = groupPath.map(idxY => libary[idxY])

        const func = (idx = 0, data: Config.ItemId[] = []): any[] => {
          return curLib[idx].map(k => {
            if (idx < maxSame - 1) {
              return func(idx + 1, [...data, k])
            } else {
              return [...data, k]
            }
          })
        }

        const groupXCalcResult: Config.ItemId[][] = func().flat(maxSame - 1)
        const result: TPrefabGroup[] = groupXCalcResult.map(path => {
          const __: TPrefabGroup = {}
          groupPath.forEach((gid, idx) => __[gid] = path[idx])
          return __
        })

        if (result.length !== 0) {
          state.prefabGroup.push(result)
          state.prefabGroupTatal += result.length
        }
        commit('log', `组选展开进度：${_idx} / ${groupY.length}`)
        console.log(`组选展开进度：${_idx} / ${groupY.length}`);
      }
      return false
    },
    /** 遴选 */
    async $selection({state, commit, dispatch}) {
      if (state.calcloading && !state.groupSelectLoading && state.calcPause) {
        return Promise.reject()
      }

      // 挑选可用组选数据
      const gsOk = state.prefabGroup.filter(k => k.length > 0);
      // 组选下标
      const gsIdx = utils.randomByNum(gsOk.length - 1);
      // 组选展开结果组
      const gsRes = gsOk[gsIdx];
      // 组选展开结果下标
      const fixIdx = utils.randomByNum(gsRes.length - 1);
      // 组选展开结果
      const fixRes = gsRes[fixIdx];
      // 随机生成一条path
      const path: (Config.ItemId|null)[] = await dispatch('$getPath', fixRes);
      console.log(`%c 生成路径：${path.map(k => String(k)).join(',')}`, 'color:red');
      // 若path可用
      if (!path.includes(null)) {
        console.log('生成一个图片', path.join(','));
        // 丢入预制池
        commit('addPrefabImage', path)
        // 使用次数 + 1
        configStore.commit('useItems', path);
      } else {
        // path不可用，移除本个
        gsRes.splice(fixIdx, 1);
        // total-1
        state.prefabGroupTatal --;
        // 若总数为0，跳出
        if (state.prefabGroupTatal <= 0) return false;
        // 检查是否有不可用列
        const checkRes: boolean = await dispatch('$checkIsHasDisabledColumns');
        // 有不可用列时，跳出
        if (checkRes) return false;
      }
      // 提示
      commit('log', `遴选中，剩余组选数量：${state.prefabGroupTatal}, 当前生成数据${state.prefabImagePool.length}个`)
      console.log(`遴选中，剩余组选数量：${state.prefabGroupTatal}, 当前生成数据${state.prefabImagePool.length}个`);
      // 休眠，降低cpu损耗
      state.prefabGroupTatal % 20 && await utils.sleep(0)
      // 循环生成，直至跳出
      await dispatch('$selection')
    },
    /** 运算前置操作 */
    async preCalcItems({state, commit}) {
      commit('clearPrefabImage')
      state.prefabGroup = []
      state.prefabGroupTatal = 0
      // HP清0
      configStore.state.currentItems.forEach(k => k.currentCount = 0)
      configStore.state.currentGroup = configStore.state.currentGroup.sort((a, b) => a.zIndex - b.zIndex)
      return false
    },
    /** 运算元素组 */
    async CalcItems({state, dispatch, commit}) {
      console.time('运算元素组');
      // 开启loading
      state.groupSelectLoading = true
      state.calcloading = true
      // 初始化
      await dispatch('preCalcItems');
      // 组选
      await dispatch('$groupSelection')
      commit('log', `组选完成，长度：${state.prefabGroupTatal}`);
      // 组选完成，关闭loading，此时允许暂停
      state.groupSelectLoading = false
      // 遴选
      dispatch('$selection').then(() => {
        commit('log', `遴选完成，长度：${state.prefabImagePool.length}`);
        console.log('遴选结果', utils.cloneOBJ(state.prefabImagePool.map(k => k.map(j => j == null ? '-' : j).join(','))));

        state.calcloading = false
      }).finally(() => {
        // 保存预制数据，无需同步操作
        dispatch('savePrefabImage')
        // 保存子元素使用情况
        configStore.dispatch('save')
        console.timeEnd('运算元素组');
      })
      return false
    },
    /** 保存预制数据 */
    savePrefabImage({state}) {
      try {
        const data = JSON.stringify(state.prefabImagePool)
        const blob = new Blob([data], {type: 'text/plain;charset=utf-8'})
        const file = new File([blob], 'prefabImage.json', {lastModified: Date.now()})

        const formData = new FormData()
        formData.append('file', file)
        service.savePrefabImage(formData)
      } catch (error) {
        console.log(error);
      }
    },
    /** 读取预制数据 */
    async fetchPrefabImage({state}) {
      const res = await service.getPrefabImage()

      state.prefabImagePool = res
    },
    /** 暂停遴选 */
    selectPause({state}) {
      if (!state.calcloading) return ElMessage.error('未开始运算！')
      else if (state.groupSelectLoading) return ElMessage.error('组选中，无法暂停！')
      else state.calcPause = true
    },
    /** 继续遴选 */
    selectContinue({state, commit, dispatch}) {
      state.calcPause = false
      dispatch('$selection').then(() => {
        // 若暂停了，则运算还在继续，反之，则停止运算loading
        state.calcloading = false
        commit('log', `遴选完成，长度：${state.prefabImagePool.length}`);
      }).finally(() => {
        // 保存预制数据，无需同步操作
        dispatch('savePrefabImage')
        // 保存子元素使用情况
        configStore.dispatch('save')
      })
    }
  },
  mutations: {
    log: (state, msg: string) => {
      const __ = [...state.calcPrefabTip, msg]

      state.calcPrefabTip = __.length > 5 ? __.slice(1, 5) : __
    },
    addPrefabImage: (state, prefabImage: Config.ItemId[]) => {
      state.prefabImagePool.push(prefabImage)
    },
    clearPrefabImage: state => {
      state.prefabImagePool = []
    },
  }
})

NFTCreatorStore.dispatch('fetchPrefabImage')

export function useNFTCreatorStore(_configStore: Store<configStoreState>) {
  configStore = _configStore

  return baseUseStore(NFTCreatorStoreKey)
}
